{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Лекция 7. Исключения"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "https://en.cppreference.com/w/cpp/language/exceptions"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "https://en.cppreference.com/w/cpp/error"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "https://apprize.info/c/professional/13.html"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<br />"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "##### Зачем нужны исключения"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Для обработки исключительных ситуаций.\n",
    "\n",
    "Как вариант - обработка ошибок."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<br />"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "###### Как пользоваться исключениями"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Нужно определиться с двумя точками в программе:\n",
    "1. Момент детекции ошибки\n",
    "2. Момент обработки ошибки"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "В момент возникновения ошибки бросаем (`throw`) любой объект, в который вкладываем описание исключительной ситуации:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```c++\n",
    "double my_sqrt(double x)\n",
    "{\n",
    "    if (x < 0)\n",
    "        throw std::invalid_argument(\"sqrt of negative doubles can not be represented in terms of real numbers\");\n",
    "    \n",
    "    ...\n",
    "}\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "В вызывающем коде оборачиваем бросающий блок в `try`-`catch`:\n",
    "\n",
    "*(обратить внимание как брошенное исключение будет обрабатываться)*"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```c++\n",
    "void run_dialogue()\n",
    "{\n",
    "    std::cout << \"enter x: \";\n",
    "    double x;\n",
    "    std::cin >> x;\n",
    "    std::cout << \"sqrt(x) = \" << my_sqrt(x) << std::endl;\n",
    "}\n",
    "\n",
    "int main()\n",
    "{\n",
    "    try\n",
    "    {\n",
    "        run_dialogue();\n",
    "    }\n",
    "    catch(const std::invalid_argument& e)\n",
    "    {\n",
    "        std::cout << \"invalid argument: \" << e.what() << std::endl;\n",
    "        return 1;\n",
    "    }\n",
    "    catch(const std::exception& e)\n",
    "    {\n",
    "        std::cout << \"common exception: \" << e.what() << std::endl;\n",
    "        return 1;\n",
    "    }\n",
    "    return 0;\n",
    "}\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Замечание**: бросать можно объекты любого типа, но рекомендуется бросать именно специальные объекты-исключения для читабельности."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Вопрос:** `std::invalid_argument` - наследник `std::exception`. Что будет, если поменять блоки-обработчики местами?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<br />"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "##### Стратегии обработки ошибок"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Есть как минимум три стратегии обработки ошибок:\n",
    "* через коды возврата\n",
    "* через исключения\n",
    "* сделать вид, что ошибок не существует\n",
    "\n",
    "Типичная реализация функции, когда ошибки обрабатываются через коды возврата:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```c++\n",
    "int get_youngest_student(std::string& student_name)\n",
    "{\n",
    "    int err_code = 0;\n",
    "    \n",
    "    // вытащить всех студентов из БД (пример: проброс ошибки)\n",
    "    std::vector<Student> students;\n",
    "    err_code = fetch_students(students);\n",
    "    if (err_code != ErrCode::OK)\n",
    "        return err_code;\n",
    "    \n",
    "    // найти самого молодого (пример: ручное управление)\n",
    "    auto youngest_it = std::min_element(students.begin(),\n",
    "                                        students.end(),\n",
    "                                        [](const auto& lhs, const auto& rhs){\n",
    "                                            return lhs.age < rhs.age;\n",
    "                                        });\n",
    "    if (youngest_it == students.end())\n",
    "        return ErrCode::NoStudents;\n",
    "    \n",
    "    // вытащить из базы его имя (пример: частичная обработка)\n",
    "    err_code = fetch_student_name(youngest_it->id, student_name);\n",
    "    if (err_code != ErrCode::OK)\n",
    "        if (err_code == ErrCode::NoObject)\n",
    "            return ErrCode::CorruptedDatabase;\n",
    "        else\n",
    "            return err_code;\n",
    "    \n",
    "    return ErrCode::OK;\n",
    "}\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Типичная реализация в случае использования исключений:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```c++\n",
    "std::string get_youngest_student()\n",
    "{\n",
    "    // вытащить всех студентов из БД (пример: проброс ошибки)\n",
    "    std::vector<Student> students = fetch_students();\n",
    "    \n",
    "    // найти самого молодого (пример: ручное управление)\n",
    "    auto youngest_it = std::min_element(students.begin(),\n",
    "                                        students.end(),\n",
    "                                        [](const auto& lhs, const auto& rhs){\n",
    "                                            return lhs.age < rhs.age;\n",
    "                                        });\n",
    "    if (youngest_it == students.end())\n",
    "        throw std::runtime_error(\"students set is empty\");\n",
    "    \n",
    "    // вытащить из базы его имя (пример: частичная обработка)\n",
    "    try\n",
    "    {\n",
    "        return fetch_student_name(youngest_it->id);\n",
    "    }\n",
    "    catch(const MyDBExceptions::NoObjectException& exception)\n",
    "    {\n",
    "        throw MyDBExceptions::CorruptedDatabase();\n",
    "    }\n",
    "}\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Типичная реализация в случае игнорирования ошибок"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```c++\n",
    "std::string get_youngest_student()\n",
    "{\n",
    "    // вытащить всех студентов из БД (пример: проброс ошибки)\n",
    "    std::vector<Student> students = fetch_students();  // не кидает исключений,\n",
    "                                                       // никак не узнать, что проблемы с доступом к базе\n",
    "    \n",
    "    // найти самого молодого (пример: ручное управление)\n",
    "    auto youngest_it = std::min_element(students.begin(),\n",
    "                                        students.end(),\n",
    "                                        [](const auto& lhs, const auto& rhs){\n",
    "                                            return lhs.age < rhs.age;\n",
    "                                        });\n",
    "    if (youngest_it == students.end())\n",
    "        return \"UNK\";  // не отделить ситуацию, когда нет студентов в базе вообще\n",
    "                       // от ситуации, когда в базе имя UNK у студента\n",
    "    \n",
    "    // вытащить из базы его имя (пример: частичная обработка)\n",
    "    return fetch_student_name(youngest_it->id);  // # не кидает исключений\n",
    "    // не отделить ситуацию, когда в таблице имён пропущен студент\n",
    "    // от ситуации, когда студент есть с именем UNK\n",
    "}\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**На практике: разумный баланс между детализацией ошибок и сложностью программы.**"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<br />"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "##### как бросать и как ловить исключения, размотка стека"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "В момент исключительной ситуации:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```c++\n",
    "double my_sqrt(double x)\n",
    "{\n",
    "    if (x < 0)\n",
    "        throw std::invalid_argument(\"sqrt can not be calculated for negatives in terms of double\");\n",
    "    ...;\n",
    "}\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Далее начинается размотка стека и поиск соответствующего catch-блока:\n",
    "\n",
    "(объяснить на примере, показать 3 ошибки в коде кроме \"oooops\")\n",
    "\n",
    "<details>\n",
    "<summary>Подсказка</summary>\n",
    "<p>\n",
    "\n",
    "1. утечка `logger`\n",
    "2. `front` без проверок\n",
    "3. порядок обработчиков\n",
    "\n",
    "</p> \n",
    "</details>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```c++\n",
    "double get_radius_of_first_polyline_point()\n",
    "{\n",
    "    auto* logger = new Logger();\n",
    "    \n",
    "    std::vector<Point> polyline = make_polyline();\n",
    "    Point p = polyline.front();\n",
    "    \n",
    "    logger->log(\"point is \" + std::to_string(p.x) + \" \" + std::to_string(p.y));\n",
    "    \n",
    "    double r = my_sqrt(p.x * p.x - p.y * p.y);  // ooops\n",
    "    \n",
    "    logger->log(\"front radius is \" + std::to_string(r));\n",
    "    delete logger;\n",
    "    \n",
    "    return r;\n",
    "}\n",
    "\n",
    "void func()\n",
    "{\n",
    "    try\n",
    "    {\n",
    "        std::cout << get_radius_of_first_polyline_point() << std::endl;\n",
    "    }\n",
    "    catch (const std::exception& e)\n",
    "    {\n",
    "        std::cout << \"unknown exception: \" << e.what() << std::endl;\n",
    "    }\n",
    "    catch (const std::invalid_argument& e)\n",
    "    {\n",
    "        std::cout << \"aren't we trying to calculate sqrt of negatve? \" << e.what() << std::endl;\n",
    "    }\n",
    "    catch (...)  // you should never do that\n",
    "    {\n",
    "        std::cout << \"what?\" << std::endl;\n",
    "    }\n",
    "}\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "__Вопрос__:\n",
    "* какие операции в коде могут кинуть исключение?\n",
    "* Как в `catch (const std::invalid_argument& e)` отличить подсчёт корня из отрицательного числа от других `std::invalid_argument`?\n",
    "* Что будет в таком варианте?\n",
    "\n",
    "```c++\n",
    "    catch (const std::invalid_argument e)\n",
    "    {\n",
    "        std::cout << \"aren't we trying to calculate sqrt of negatve? \" << e.what() << std::endl;\n",
    "    }\n",
    "```\n",
    "\n",
    "* а в таком?\n",
    "\n",
    "```c++\n",
    "    catch (std::invalid_argument& e)\n",
    "    {\n",
    "        std::cout << \"aren't we trying to calculate sqrt of negatve? \" << e.what() << std::endl;\n",
    "    }\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<br />"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "##### noexcept"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Если функция не бросает исключений, желательно пометить её `noexcept`.\n",
    "\n",
    "Вызов такой функции будет чуть дешевле и объём бинарного файла чуть меньше (не нужно генерировать кода поддержки исключений).\n",
    "\n",
    "Что будет если `noexcept` - функция попытается бросить исключение?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```c++\n",
    "int get_sum(const std::vector<int>& v) noexcept\n",
    "{\n",
    "    return std::reduce(v.begin(), v.end());\n",
    "}\n",
    "\n",
    "int get_min(const std::vector<int>& v) noexcept\n",
    "{\n",
    "    if (v.empty())\n",
    "        throw std::invalid_argument(\"can not find minimum in empty sequence\");\n",
    "    \n",
    "    return *std::min_element(v.begin(), v.end());\n",
    "}\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<details>\n",
    "<summary>Ответ</summary>\n",
    "\n",
    "Вызов std::terminate. Дальше продолжать выполнение программы нельзя, т.к. внешнему коду пообещали, что функция исключений не бросает.\n",
    "\n",
    "При этом, если компилятор не может доказать, что тело функции не бросает исключений, он генерирует try-catch блок на всю функцию с std::terminate в catch.\n",
    "\n",
    "</details>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<br />"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "##### стандартные и собственные классы исключений"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Для стандартных исключений в С++ выделен базовый класс `std::exception`:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```c++\n",
    "class exception\n",
    "{\n",
    "public:\n",
    "    exception() noexcept;\n",
    "    virtual ~exception() noexcept;\n",
    "\n",
    "    exception(const exception&) noexcept;\n",
    "    exception& operator=(const exception&) noexcept;\n",
    "\n",
    "    virtual const char* what() const noexcept;\n",
    "};\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Остальные стандартные исключения наследуются от него:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![](std_exception_hierarchy.jpg)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Как бросать стандартные исключения:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```c++\n",
    "// проверить на ноль перед делением\n",
    "if (den == 0)\n",
    "    throw std::runtime_error(\"unexpected integer division by zero\");\n",
    "\n",
    "// проверить аргументы на корректность\n",
    "bool binsearch(const int* a, const int n, const int value)\n",
    "{\n",
    "    if (n < 0)\n",
    "        throw std::invalid_argument(\"unexpexted negative array size\");\n",
    "    ...;\n",
    "}\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<br />"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Зачем свои классы исключений?\n",
    "* бОльшая детализация ошибки\n",
    "* возможность добавить информацию к классу исключения"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Рекомендации:\n",
    "* свои классы наследовать от `std::exception`\n",
    "* если возможно - организовать свои исключения в иерархию (чтобы была возможность ловить общую ошибку библиотеки или более детальные)\n",
    "* если возможно - предоставить информацию для анализа и восстановления"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Рассмотрим пример - вы пишете свой читатель-писатель json-ов (зачем? их миллионы уже!)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```c++\n",
    "namespace myjson\n",
    "{\n",
    "    // общий наследник исключений вашей библиотеки парсинга,\n",
    "    // чтобы пользователи могли просто отловить события\n",
    "    // \"в этой билиотеке что-то пошло не так\"\n",
    "    class MyJsonException : public std::exception {};\n",
    "    \n",
    "    // исключение для случая, когда при запросе к полю у объекта\n",
    "    // поле отсутствовало\n",
    "    //\n",
    "    // можно дать обработчику исключения больше информации\n",
    "    // для более разумной обработки ситуации или хотя бы\n",
    "    // более подробного логирования проблемы\n",
    "    class FieldNotFound : public MyJsonException\n",
    "    {\n",
    "    public:\n",
    "        FieldNotFound(std::string parent_name, std::string field_name);\n",
    "\n",
    "        const std::string& parent_name() const noexcept;\n",
    "        const std::string& field_name() const noexcept;\n",
    "        \n",
    "    private:\n",
    "        std::string parent_name_;\n",
    "        std::string field_name_;\n",
    "    };\n",
    "    \n",
    "    // исключение для ошибок при парсинге json-строки\n",
    "    //\n",
    "    // можно дать больше информации, на каком символе\n",
    "    // обломился парсинг строки\n",
    "    class ParsingException : public MyJsonException\n",
    "    {\n",
    "    public:\n",
    "        ParsingException(int symbol_ix);\n",
    "        \n",
    "        int symbol_ix() const noexcept;\n",
    "        \n",
    "    private:\n",
    "        int symbol_ix_;\n",
    "    };\n",
    "    \n",
    "    // исключение для ошибок при парсинге int-а - сужение |ParsingException|\n",
    "    class IntegerOverflowOnParsing  : public ParsingException {};\n",
    "    \n",
    "    // и т.д.\n",
    "    \n",
    "}  // namespace myjson\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Опять же, на практике выбирают баланс между детализацией и сложностью.\n",
    "\n",
    "Чем глубже детализация, тем сложнее программа, но с определённого уровня пользы от большей детализации мало."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<br />"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "##### исключения в конструкторах и деструкторах"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Почему исключения полезны в конструкторах?\n",
    "\n",
    "Потому что у конструктора нет другого способа сообщить об ошибке!"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```c++\n",
    "std::vector<int> v = {1, 2, 3, 4, 5};\n",
    "// нет другого (нормального) способа сообщить вызываемому коду,\n",
    "// что памяти не хватило и вектор не создан, только бросить\n",
    "// исключение\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Пример**: на порядок вызова конструкторов и деструкторов"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```c++\n",
    "class M { ...; };\n",
    "\n",
    "class B { ...; };\n",
    "\n",
    "class D : public B {\n",
    "public:\n",
    "    D() {\n",
    "        throw std::runtime_error(\"error\");\n",
    "    }\n",
    "    \n",
    "private:\n",
    "    M m_;\n",
    "};\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Какие конструкторы и деструкторы будут вызваны?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```c++\n",
    "try {\n",
    "    D d;\n",
    "}\n",
    "catch (const std::exception& e) {\n",
    "}\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "А если так?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```c++\n",
    "class M { ...; };\n",
    "\n",
    "class B { ...; };\n",
    "\n",
    "class D : public B {\n",
    "public:\n",
    "    D() : D(0) {\n",
    "        throw std::runtime_error(\"error\");\n",
    "    }\n",
    "    \n",
    "    D(int x) {}\n",
    "    \n",
    "private:\n",
    "    M m_;\n",
    "};\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Что с исключениями из деструкторов?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```c++\n",
    "class D\n",
    "{\n",
    "public:\n",
    "    D() {}\n",
    "    ~D() {\n",
    "        throw std::runtime_error(\"can not free resource\");\n",
    "    }    \n",
    "};\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "* Бросать исключения из деструкторов - \"плохо\"\n",
    "* По умолчанию деструктор - `noexcept` (если нет специфических проблем с базовыми классами и членами)\n",
    "* Если при размотке стека из деструктора объекта бросается исключение, программа завершается с `std::terminate` по стандарту: https://en.cppreference.com/w/cpp/language/destructor (раздел Exceptions) \"you can not fail to fail\""
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "__Упражение__: чтобы понять, почему деструктору нельзя кидать исключения, попробуйте на досуге представить, как корректно реализовать `resize` у `std::vector<T>`, если `~T` иногда кидает исключение"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<br />"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "##### гарантии при работе с исключениями"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "* `nothrow` - функция не бросает исключений\n",
    "\n",
    "```c++\n",
    "int swap(int& x, int& y)\n",
    "```\n",
    "\n",
    "\n",
    "* `strong` - функция отрабатывает как транзакция: если из функции вылетает исключение, состояние программы откатывается на момент как до вызова функции.\n",
    "\n",
    "```c++\n",
    "std::vector::push_back\n",
    "```\n",
    "\n",
    "* `basic` - если из функции вылетает исключение, программа ещё корректна (инварианты сохранены). Может потребоваться очистка.\n",
    "\n",
    "\n",
    "```c++\n",
    "void write_to_csv(const char* filename)\n",
    "{\n",
    "    std::ofsteam ofs(filename);\n",
    "    ofs << \"id,name,age\" << std::endl;\n",
    "    \n",
    "    std::vector<std::string> names = ...; // bad_alloc\n",
    "    ofs << ...;\n",
    "}\n",
    "```\n",
    "\n",
    "* `no exception guarantee` - если из функции вылетает исключение, молитесь\n",
    "\n",
    "```\n",
    "любой production код (история про обработку ошибок в файловых системах)\n",
    "```\n",
    "\n",
    "* `exception-neutral` - только для шаблонных компонент - пропускает сквозь себя все исключения, которые кидают шаблонные параметры\n",
    "\n",
    "```c++\n",
    "std::min_element(v.begin(), v.end())\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<br />"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "##### стоимость исключений"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Зависит от реализации.\n",
    "\n",
    "\"В интернете пишут, что\" исключения в основных компиляторах реализованы по принципу (бесплатно, пока не вылетел exception, дорого, если вылетел). При этом при выбросе исключений формируются специальные exception frame-ы, осуществляется поиск handler-ов и cleanup-процедур по заранее сгенерированным компилятором таблицам.\n",
    "\n",
    "Подробнее:\n",
    "* https://stackoverflow.com/questions/13835817/are-exceptions-in-c-really-slow\n",
    "* https://mortoray.com/2013/09/12/the-true-cost-of-zero-cost-exceptions/"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "При этом код обслуживания исключений тоже надо сгенерировать. Статья как в microsoft провели исследование сколько занимает код обслуживания механизма исключений (спойлер: для конкретной билиотеки в районе 26% - зависит от кол-ва исключений, кол-ва бросающих исключения функций, кол-ва вызовов throw, сложности объектов на стеке и т.д.) и как его сократили где-то в 2 раза:\n",
    "\n",
    "https://devblogs.microsoft.com/cppblog/making-cpp-exception-handling-smaller-x64/"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<br />"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "##### noexcept-move-операции"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Пояснить на классическом примере `std::vector::push_back` каким образом объявление move-операций `noexcept` позволяет ускорить программу:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![](vector_noexcept.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Аналогично допустимы оптимизации при работе с std::any для nothrow move constructible типов<br />\n",
    "https://en.cppreference.com/w/cpp/utility/any"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<br />"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "##### правила хорошего тона при реализации исключений"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "* Деструкторы не должны бросать исключений. Можете помечать их `noexcept` или знать, что компилятор во многих случаях автоматически добавляет `noexcept` к деструкторам.\n",
    "* Реализовывать и помечать move-операции как `noexcept`\n",
    "* Реализовывать и помечать default constructor как `noexcept` (cppcoreguildelines для скорости)\n",
    "* `noexcept` everything you can!\n",
    "* Цитата с cppcoreguidelines: `If you know that your application code cannot respond to an allocation failure, it may be appropriate to add noexcept even on functions that allocate.` Объяснить её смысл про восстановление после ошибки и почему \"нелогичность\" здесь полезна.\n",
    "* Пользовательские классы исключений наследовать от `std::exception` или его подклассов\n",
    "* Ловить исключений по const-ссылкам\n",
    "* `throw;` вместо `throw e;` из catch-блока, когда нужен rethrow\n",
    "* Исключения являются частью контракта (или спецификации) функции! Желательно их протестировать.\n",
    "* Использовать исключения для исключительных ситуаций, а не для естественного потока выполнения.\n",
    "  * Плохой код:\n",
    "    * исключение чтобы прервать цикл\n",
    "    * исключение чтобы сделать особое возвращаемое значение из функции\n",
    "  * Приемлемо:\n",
    "    * исключение чтобы сообщить об ошибке\n",
    "    * исключение чтобы сообщить о нарушении контракта (объяснить, почему это не лучший вариант использования исключений)\n",
    "* Исключения служат для того чтобы восстановиться после ошибки и продолжить работу:\n",
    "  * пропустить некритичные действия. Пример:\n",
    "    * отобразить телефон организации в информационном листе)\n",
    "  * fallback с восстановлением или откатом:\n",
    "    * memory-consuming алгоритмы\n",
    "    * message box-ы об ошибке (не удалось открыть документ в msword)\n",
    "  * красиво умереть на критических ошибках:\n",
    "    * memory allocation on game start\n",
    "    * некорректная команда в текстовом интерпретаторе\n",
    "* Глобальный try-catch (плюсы: программа завершается без падений, деструкторы объектов на стеке будут позваны (если нет catch-блока, вызов процедура размотки стека может не выполняться - лазейка стандарта). минус: не будет создан crashdump для анализа проблемы):\n",
    "\n",
    "    ```c++\n",
    "    int main() {\n",
    "        try {\n",
    "            ...\n",
    "        } catch(const std::exception& e) {\n",
    "            std::cout << \"Until your last breath!\\n\";\n",
    "            std::cout << \"ERROR: \" << e.what() << std::endl;\n",
    "            return 1;\n",
    "        }\n",
    "        return 0;\n",
    "    }\n",
    "    ```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<br />"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Полезные материалы**:\n",
    "* [C++ Russia: Роман Русяев — Исключения C++ через призму компиляторных оптимизаций.](https://www.youtube.com/watch?v=ItemByR4PRg) \n",
    "* [CppCon 2018: James McNellis “Unwinding the Stack: Exploring How C++ Exceptions Work on Windows”](https://youtu.be/COEv2kq_Ht8)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<br />"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Замечания после лекции**:\n",
    "1. В итераторах не для итерирования черкнуть про то, как могут выглядеть операторы. Без этого улавливается хуже.\n",
    "2. В стратегиях обработки ошибок сначала сказать, что за задача будет решаться: вытащить всех / найти минимум / вытащить имя.\n",
    "3. Про собственные классы исключений материал подсократить.\n",
    "4. Про исключения в деструкторе и вектор - добавить картинку (либо рисовать от руки)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
